package cn.lg.soar.common.util.jackson.annotations;

import cn.lg.soar.common.util.DatetimeUtil;
import cn.lg.soar.common.util.jackson.deserializer.SDateDeserializer;
import cn.lg.soar.common.util.jackson.deserializer.SLocalDateDeserializer;
import cn.lg.soar.common.util.jackson.deserializer.SLocalDateTimeDeserializer;
import cn.lg.soar.common.util.jackson.deserializer.SLocalTimeDeserializer;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * 数值序列化
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
@JacksonAnnotationsInside
@JsonSerialize(using = SoarFormat.Serializer.class)
@JsonDeserialize(using = SoarFormat.Deserializer.class)
public @interface SoarFormat {

    /**
     * 格式（支持数值格式、日期时间格式、数据脱敏、正则替换）
     * 正则替换必须成对
     * 常用格式参考：cn.lg.soar.common.constant.Format
     * 例：
     * 数值：SoarFormat(",###")
     * 日期时间：SoarFormat("yyyy-MM-dd HH:mm:ss")
     * 脱敏：SoarFormat("$TM:3****4")
     * 替换：SoarFormat({ "杀", "噶" })
     * @return
     */
    String[] value() default "";

    /**
     * 配置序列化格式（默认同 value 属性）
     * @return
     */
    String[] serialize() default "";

    /**
     * 配置反序列化格式（默认同 value 属性）
     * @return
     */
    String[] deserialize() default "";

    /**
     * 序列化
     */
    class Serializer extends JsonSerializer<Object> implements ContextualSerializer {
        @Override
        public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
                                                  final BeanProperty beanProperty) throws JsonMappingException {
            if (beanProperty != null) {
                SoarFormat annotation = beanProperty.getAnnotation(SoarFormat.class);
                if (annotation == null) {
                    annotation = beanProperty.getContextAnnotation(SoarFormat.class);
                }
                if (annotation == null) {
                    return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
                }
                // 获取格式
                String[] serialize = annotation.serialize();
                if (Utils.isEmpty(serialize)) {
                    serialize = annotation.value();
                }
                // 序列化配置为空 并且 反序列化不为空时默认不启用序列化
                if (Utils.isEmpty(serialize) && !Utils.isEmpty(annotation.deserialize())) {
                    return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
                }
                // 创建序列化器
                String[] value = serialize;
                Class<?> rawClass = beanProperty.getType().getRawClass();
                // 正则匹配
                if (value.length != 1) {
                    if ((value.length & 0x01) == 1) {
                        throw new  RuntimeException("格式错误，格式数量只可为一或者成对，此处出现了：" + value.length);
                    }
                    return new JsonSerializer<Object>() {
                        @Override
                        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                            String s = o.toString();
                            for (int i = 0; i < value.length; i+=2) {
                                s = s.replaceAll(value[i], value[i+1]);
                            }
                            jsonGenerator.writeString(s);
                        }
                    };
                }
                String format = value[0];
                // 时间类型处理
                if (TemporalAccessor.class.isAssignableFrom(rawClass)) {
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(Utils.getDateFormat(rawClass, format));
                    return new JsonSerializer<TemporalAccessor>() {
                        @Override
                        public void serialize(TemporalAccessor value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                            jsonGenerator.writeString(formatter.format(value));
                        }
                    };
                }
                if (Date.class.isAssignableFrom(rawClass)) {
                    String finalFormat = "".equals(format) ? "yyyy-MM-dd HH:mm:ss" : format;
                    return new JsonSerializer<Date>() {
                        @Override
                        public void serialize(Date value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                            jsonGenerator.writeString(DatetimeUtil.format(value, finalFormat));
                        }
                    };
                }
                // 数值处理
                if (Utils.isNumber(rawClass)) {
                    DecimalFormat formatter = new DecimalFormat(Utils.getNumberFormat(rawClass, format));
                    return new JsonSerializer<Number>() {
                        @Override
                        public void serialize(Number value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                            jsonGenerator.writeString(formatter.format(value));
                        }
                    };
                }
                // 脱敏处理
                if (Utils.isDesensitization(format)) {
                    format = format.substring(4);
                    String[] split = format.split("\\D+");
                    int preLen = Integer.parseInt(split[0]);
                    int sufLen = Integer.parseInt(split[1]);
                    String replacement = format.replaceAll("\\d", "");
                    return new JsonSerializer<Object>() {
                        @Override
                        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                            String string = o.toString();
                            if (string.length() == 0) {
                                jsonGenerator.writeString(string);
                            } else {
                                jsonGenerator.writeString(
                                        string.substring(0, preLen) + replacement + string.substring(string.length() - sufLen)
                                );
                            }
                        }
                    };
                }
            }
            return serializerProvider.findNullValueSerializer(null);
        }
        @Override
        public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeObject(value);
        }
    }

    /**
     * 反序列化
     */
    class Deserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
            if (beanProperty != null) {
                SoarFormat annotation = beanProperty.getAnnotation(SoarFormat.class);
                if (annotation == null) {
                    annotation = beanProperty.getContextAnnotation(SoarFormat.class);
                }
                if (annotation == null) {
                    return deserializationContext.findContextualValueDeserializer(beanProperty.getType(), beanProperty);
                }
                // 获取格式
                String[] deserialize = annotation.deserialize();
                if (Utils.isEmpty(deserialize)) {
                    deserialize = annotation.value();
                }
                // 反序列化配置为空 并且 序列化不为空时默认不启用反序列化
                if (Utils.isEmpty(deserialize) && !Utils.isEmpty(annotation.serialize())) {
                    return deserializationContext.findContextualValueDeserializer(beanProperty.getType(), beanProperty);
                }
                // 创建反序列化器
                String[] value = deserialize;
                Class<?> rawClass = beanProperty.getType().getRawClass();
                // 正则匹配
                if (value.length != 1) {
                    if ((value.length & 0x01) == 1) {
                        throw new  RuntimeException("格式错误，格式数量只可为一或者成对，此处出现了：" + value.length);
                    }
                    return new JsonDeserializer<Object>() {
                        @Override
                        public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                            String s = jsonParser.getText();
                            for (int i = 0; i < value.length; i+=2) {
                                s = s.replaceAll(value[i], value[i+1]);
                            }
                            return s;
                        }
                    };
                }
                String format = value[0];
                if (LocalDateTime.class.equals(rawClass)) {
                    return new SLocalDateTimeDeserializer(Utils.getDateFormat(rawClass, format));
                } else if (LocalDate.class.equals(rawClass)) {
                    return new SLocalDateDeserializer(Utils.getDateFormat(rawClass, format));
                } else if (LocalTime.class.equals(rawClass)) {
                    return new SLocalTimeDeserializer(Utils.getDateFormat(rawClass, format));
                } else if (Date.class.equals(rawClass)) {
                    return new SDateDeserializer(Utils.getDateFormat(rawClass, format));
                }
                /*if (TemporalAccessor.class.isAssignableFrom(rawClass)) {
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(Utils.getDateFormat(rawClass, format));
                    return new JsonDeserializer<TemporalAccessor>() {
                        @Override
                        public TemporalAccessor deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                            return formatter.parse(jsonParser.getText());
                        }
                    };
                }*/
                if (Utils.isNumber(rawClass)) {
                    // 数值处理
                    DecimalFormat formatter = new DecimalFormat(Utils.getNumberFormat(rawClass, format));
                    return new JsonDeserializer<Number>() {
                        @Override
                        public Number deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                            try {
                                return formatter.parse(jsonParser.getText());
                            } catch (ParseException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    };
                }
            }
            return deserializationContext.findContextualValueDeserializer(beanProperty.getType(), beanProperty);
        }

        @Override
        public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
            return jsonParser.getText();
        }
    }
    class Utils {
        private static final Set<String> numbers = new HashSet<>();
        static {
            numbers.add("double");
            numbers.add("float");
            numbers.add("long");
            numbers.add("int");
            numbers.add("short");
            numbers.add("byte");
        }
        public static boolean isNumber(Class<?> clazz) {
            if (Number.class.isAssignableFrom(clazz)) {
                return true;
            }
            return numbers.contains(clazz.getName());
        }
        public static boolean isDesensitization(String format) {
            return format.startsWith("$TM:");
        }
        public static String getNumberFormat(Class<?> clazz, String format) {
            if ("".equals(format)) {
                if (
                        Integer.class.equals(clazz) ||
                                Long.class.equals(clazz) ||
                                Short.class.equals(clazz) ||
                                Byte.class.equals(clazz) ||
                                BigInteger.class.equals(clazz) ||
                                "long".equals(clazz.getName()) ||
                                "int".equals(clazz.getName()) ||
                                "short".equals(clazz.getName()) ||
                                "byte".equals(clazz.getName())
                ) {
                    return ",###";
                } else {
                    return ",###.#";
                }
            }
            return format;
        }
        public static String getDateFormat(Class<?> clazz, String format) {
            if ("".equals(format)) {
                if (LocalDate.class.equals(clazz)) {
                    return "yyyy-MM-dd";
                } else if (LocalTime.class.equals(clazz)) {
                    return "HH:mm:ss";
                } else {
                    return "yyyy-MM-dd HH:mm:ss";
                }
            }
            return format;
        }

        public static boolean isEmpty(String[] value) {
            return value.length == 1 && "".equals(value[0]);
        }
    }
}
